/*
 * MIT License
 *
 * Copyright (c) 2025 milkpotatoes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <string_view>
#include <uriparser/Uri.h>
#include <vector>

#include "runner_utils/exceptions.h"
#include "runner_utils/log.h"
#include "runner_utils/napi_utils.h"
#include "runner_utils/utils.h"

#include "js_utils/js_uri.h"

napi_value JsUri::Init(napi_env env, napi_value exports)
{
    std::vector<napi_property_descriptor> properties{
        {"_innerParse", nullptr, InnerParse, nullptr, nullptr, nullptr, napi_default_jsproperty, nullptr},
    };
    napi_define_properties(env, exports, properties.size(), properties.data());
    return exports;
}

napi_value TextRange2String(napi_env env, const UriTextRangeA &range, napi_value empty = nullptr)
{
    if (range.first == nullptr || range.afterLast == nullptr || range.first >= range.afterLast)
    {
        return empty;
    }
    return utils::CreateString(env, std::string_view(range.first, range.afterLast - range.first));
}

std::string UriPath2String(const UriPathSegmentA *path)
{
    if (path == nullptr)
    {
        return "";
    }
    std::string result;
    for (auto head = path; head != nullptr; head = head->next)
    {
        result += "/";
        result.append(head->text.first, head->text.afterLast - head->text.first);
    }
    return result;
}

napi_value UriUriA2Array(napi_env env, const UriUriA &uri)
{
    napi_value result = utils::CreateArray(env);
    napi_value empty = utils::CreateStringUtf8(env, "");
    napi_set_element(env, result, 0, TextRange2String(env, uri.scheme, empty));   // 0: protocol
    napi_set_element(env, result, 1, TextRange2String(env, uri.userInfo, empty)); // 1: userinfo
    napi_set_element(env, result, 2, TextRange2String(env, uri.hostText, empty)); // 2: hostname
    napi_set_element(env, result, 3, TextRange2String(env, uri.portText, empty)); // 3: port
    if (uri.pathHead != nullptr)
    {
        auto path = UriPath2String(uri.pathHead);
        napi_set_element(env, result, 4, path.empty() ? empty : utils::CreateString(env, path)); // 4: pathname
    }
    else
    {
        napi_set_element(env, result, 4, empty); // 4: pathname
    }
    napi_set_element(env, result, 5, TextRange2String(env, uri.query, empty));    // 5: query
    napi_set_element(env, result, 6, TextRange2String(env, uri.fragment, empty)); // 5: hash
    return result;
}

DEF_NAPI_CALLBACK(JsUri::InnerParse, info)
{
    std::string url = utils::GetValueStringUtf8(info.GetEnv(), info[0]);
    UriUriA uri;
    const char *errorPos;
    if (int code = uriParseSingleUriA(&uri, url.data(), &errorPos); code != URI_SUCCESS)
    {
        DEFAULT_LOGGER(DEBUG) << "Invalid URL, code: " << code << ", pos: " << (errorPos - url.data());
        napi_throw_type_error(info.GetEnv(), exceptions::code::INVALID_ARG_TYPE, "Invalid URL");
        return nullptr;
    }
    utils::StackCaller guarder([&uri] { uriFreeUriMembersA(&uri); });
    if (info.GetArgc() > 1)
    {
        std::string baseUrl = utils::GetValueStringUtf8(info.GetEnv(), info[1]);
        UriUriA base;
        if (int code = uriParseSingleUriA(&base, baseUrl.data(), &errorPos); code != URI_SUCCESS)
        {
            DEFAULT_LOGGER(DEBUG) << "Invalid base URL, code: " << code << ", pos: " << (errorPos - baseUrl.data());
            napi_throw_type_error(info.GetEnv(), exceptions::code::INVALID_ARG_TYPE, "Invalid base URL");
            return nullptr;
        }
        if (base.scheme.first == nullptr)
        {
            napi_throw_type_error(info.GetEnv(), exceptions::code::INVALID_ARG_TYPE, "Invalid base URL");
            uriFreeUriMembersA(&base);
            return nullptr;
        }
        utils::StackCaller baseGuarder([&base] { uriFreeUriMembersA(&base); });
        uriNormalizeSyntaxA(&uri);
        uriNormalizeSyntaxA(&base);
        UriUriA abs;
        if (int code = uriAddBaseUriA(&abs, &uri, &base); code != URI_SUCCESS)
        {
            DEFAULT_LOGGER(DEBUG) << "Failed to resolve url, code: " << code
                                  << ", pos: " << (errorPos - baseUrl.data());
            napi_throw_type_error(info.GetEnv(), exceptions::code::INVALID_ARG_TYPE, "Failed to resolve url");
            return nullptr;
        }
        utils::StackCaller absGuarder([&abs] { uriFreeUriMembersA(&abs); });
        return UriUriA2Array(info.GetEnv(), abs);
    }
    return UriUriA2Array(info.GetEnv(), uri);
}
